 /*******************************************************************************
  * Copyright (c) 2003, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM - Initial API and implementation
  * Jeremiah Lott (jeremiah.lott@timesys.com) - fix for deadlock bug 76378
  *
  *******************************************************************************/
 package org.eclipse.ui.internal;

 import org.eclipse.core.runtime.jobs.LockListener;
 import org.eclipse.swt.widgets.Display;

 /**
  * The UI lock listener is used to prevent the UI thread from deadlocking on
  * a lock when the thread owning the lock is attempting to syncExec.
  */
 public class UILockListener extends LockListener {

     /**
      * The Queue is the construct that keeps track of Semaphores.
      */
     public class Queue {
         private static final int BASE_SIZE = 8;

         protected Semaphore[] elements = new Semaphore[BASE_SIZE];

         protected int head = 0;

         protected int tail = 0;

         /**
          * Add the semaphore to the queue.
          * @param element
          */
         public synchronized void add(Semaphore element) {
             int newTail = increment(tail);
             if (newTail == head) {
                 grow();
                 newTail = tail + 1;
             }
             elements[tail] = element;
             tail = newTail;
         }

         private void grow() {
             int newSize = elements.length * 2;
             Semaphore[] newElements = new Semaphore[newSize];
             if (tail >= head) {
                 System.arraycopy(elements, head, newElements, head, size());
             } else {
                 int newHead = newSize - (elements.length - head);
                 System.arraycopy(elements, 0, newElements, 0, tail + 1);
                 System.arraycopy(elements, head, newElements, newHead,
                         (newSize - newHead));
                 head = newHead;
             }
             elements = newElements;
         }

         private int increment(int index) {
             return (index == (elements.length - 1)) ? 0 : index + 1;
         }

         /**
          * Remove the next semaphore to be woken up.
          * @return
          */
         public synchronized Semaphore remove() {
             if (tail == head) {
                 return null;
             }
             Semaphore result = elements[head];
             elements[head] = null;
             head = increment(head);
             //reset the queue if it is empty and it has grown
 if (tail == head && elements.length > BASE_SIZE) {
                 elements = new Semaphore[BASE_SIZE];
                 tail = head = 0;
             }
             return result;
         }

         private int size() {
             return tail > head ? (tail - head)
                     : ((elements.length - head) + tail);
         }
     }

     protected Display display;

     protected final Queue pendingWork = new Queue();

     protected Semaphore currentWork = null;

     protected Thread ui;

     /**
      * Create a new instance of the receiver.
      * @param display
      */
     public UILockListener(Display display) {
         this.display = display;
     }

     public void aboutToRelease() {
         if (isUI()) {
             ui = null;
         }
     }

     public boolean aboutToWait(Thread lockOwner) {
         if (isUI()) {
             // If a syncExec was executed from the current operation, it
 // has already acquired the lock. So, just return true.
 if (currentWork != null
                     && currentWork.getOperationThread() == lockOwner) {
                 return true;
             }
             ui = Thread.currentThread();
             try {
                 doPendingWork();
             } finally {
                 //UI field may be nulled if there is a nested wait during execution
 //of pending work, so make sure it is assigned before we start waiting
 ui = Thread.currentThread();
             }
         }
         return false;
     }

     void addPendingWork(Semaphore work) {
         pendingWork.add(work);
     }

     /**
      * Should always be called from the UI thread.
      */
     void doPendingWork() {
         //clear the interrupt flag that we may have set in interruptUI()
 Thread.interrupted();
         Semaphore work;
         while ((work = pendingWork.remove()) != null) {
             //remember the old current work before replacing, to handle
 //the nested waiting case (bug 76378)
 Semaphore oldWork = currentWork;
             try {
                 currentWork = work;
                 Runnable runnable = work.getRunnable();
                 if (runnable != null) {
                     runnable.run();
                 }

             } finally {
                 currentWork = oldWork;
                 work.release();
             }
         }
     }

     void interruptUI() {
         display.getThread().interrupt();
     }

     boolean isLockOwner() {
         return isLockOwnerThread();
     }

     boolean isUI() {
         return (!display.isDisposed())
                 && (display.getThread() == Thread.currentThread());
     }

     boolean isUIWaiting() {
         return (ui != null) && (Thread.currentThread() != ui);
     }
 }

